package service_register

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	logger "gitee.com/zaper/private-logger"
	"gitee.com/zaper/service-config/constant"
	"gitee.com/zaper/service-config/service/comm_handler"
	"gitee.com/zaper/service-config/service/etcd"
	"gitee.com/zaper/service-config/service/model"
	"gitee.com/zaper/service-config/util"
	clientv3 "go.etcd.io/etcd/client/v3"
	"os"
	"strconv"
)

type RegisterAgent struct {
	attr       *registerAttr
	clientEtcd *clientv3.Client
	lease      clientv3.Lease
}

func NewRegisterAgent(cliEtcd *clientv3.Client, operations ...Operation) *RegisterAgent {
	regAttr := &registerAttr{}
	for _, o := range operations {
		o(regAttr)
	}
	return &RegisterAgent{
		attr:       regAttr,
		clientEtcd: cliEtcd,
	}
}

type Operation func(*registerAttr)

type registerAttr struct {
	ctx       context.Context
	nameSpace string
	ttl       int64
	maxRetry  int
}

func WithCtx(ctx context.Context) Operation {
	return func(attr *registerAttr) {
		attr.ctx = ctx
	}
}

func WithNameSpace(nameSpace string) Operation {
	return func(attr *registerAttr) {
		attr.nameSpace = nameSpace
	}
}

func WithTTl(ttl int64) Operation {
	return func(attr *registerAttr) {
		attr.ttl = ttl
	}
}

func WithMaxRetry(maxRetry int) Operation {
	return func(attr *registerAttr) {
		attr.maxRetry = maxRetry
	}
}

func (r *RegisterAgent) RegisterRouter(service *model.ServiceModel) int {
	if service.LeaseId == "" || service.LeaseId == "-1" {
		return model.Register
	}
	resp, err := r.clientEtcd.Get(r.attr.ctx, r.GetEtcdServiceKey(service.ServiceId))
	if err != nil {
		return model.Error
	}
	if len(resp.Kvs) != 1 {
		return model.Error
	}
	var service1 model.ServiceModel
	err = json.Unmarshal(resp.Kvs[0].Value, &service1)
	if err != nil {
		return model.Error
	}
	if service1.ServiceId == service.ServiceId {
		return model.KeepAlive
	}
	return model.Register
}

func (r *RegisterAgent) Register(service *model.ServiceModel) (clientv3.LeaseID, error) {
	service.TimeStr = util.GetCurrentTime()
	resp, err := r.clientEtcd.Grant(r.attr.ctx, r.attr.ttl)
	if err != nil {
		logger.Errorf("Grant lease fail, err is %v", err)
		return -1, errors.New("grant lease fail")
	}
	_, err = r.clientEtcd.Put(r.attr.ctx, r.GetEtcdServiceKey(service.ServiceId),
		parseServiceInfo(service), clientv3.WithLease(resp.ID))
	if err != nil {
		logger.Errorf("Put serviceName fail, err is %v", err)
		return -1, errors.New("put serviceName fail")
	}
	if !registerPostConfig(service.ServiceId) {
		logger.Errorf("register fail, notify config fail")
		return -1, errors.New("notify config fail")
	}
	return resp.ID, nil
}

func (r *RegisterAgent) KeepAlive(service *model.ServiceModel) (clientv3.LeaseID, error) {
	leaseIdInt, _ := strconv.ParseInt(service.LeaseId, 10, 64)
	leaseId := clientv3.LeaseID(leaseIdInt)
	for i := 0; i < r.attr.maxRetry; i++ {
		_, err := r.clientEtcd.KeepAliveOnce(r.attr.ctx, leaseId)
		if err == nil {
			return leaseId, nil
		}
	}
	lease, err := r.Register(service)
	if err == nil {
		return -1, errors.New("keepalive fail")
	}
	return lease, nil
}

func (r *RegisterAgent) GetEtcdServiceKey(serviceName string) string {
	return fmt.Sprintf("/%s/%s", r.attr.nameSpace, serviceName)
}

func parseServiceInfo(service *model.ServiceModel) string {
	serviceJson, err := json.Marshal(service)
	if err != nil {
		return ""
	}
	return string(serviceJson)
}

func registerPostConfig(serviceId string) bool {
	logger.Infof("register notify all config begin")
	key := fmt.Sprintf("/%s", constant.ConfigName)
	configName := fmt.Sprintf("%s_%s", constant.ConfigName, serviceId)
	return comm_handler.ChangeHandler(key, configName, []string{serviceId}, clientv3.WithPrefix())
}

func GetServiceIdList() []string {
	cli, err := etcd.GetEtcdClient()
	if err != nil {
		return nil
	}
	registerKey := fmt.Sprintf("/%s", os.Getenv(constant.ConfigRegisterKey))
	resp, err := cli.Get(context.Background(), registerKey, clientv3.WithPrefix())
	if err != nil {
		return nil
	}

	var serviceIds []string
	for _, kv := range resp.Kvs {
		var service model.ServiceModel
		err = json.Unmarshal(kv.Value, &service)
		if err != nil {
			logger.Errorf("config service unmarshal fail %v", err)
			continue
		}
		serviceIds = append(serviceIds, service.ServiceId)
	}
	return serviceIds
}
